/*
 * File    : PluginPEPeerWrapper.java
 * Created : 01-Dec-2003
 * By      : parg
 * 
 * Azureus - a Java Bittorrent client
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.gudy.azureus2.pluginsimpl.local.peers;

/**
 * @author parg
 *
 */

import java.util.HashMap;
import java.util.List;

import org.gudy.azureus2.core3.logging.LogRelation;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.PEPeerListener;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.plugins.messaging.Message;
import org.gudy.azureus2.plugins.network.Connection;
import org.gudy.azureus2.plugins.network.ConnectionStub;
import org.gudy.azureus2.plugins.network.RateLimiter;
import org.gudy.azureus2.plugins.peers.Peer;
import org.gudy.azureus2.plugins.peers.PeerEvent;
import org.gudy.azureus2.plugins.peers.PeerListener;
import org.gudy.azureus2.plugins.peers.PeerListener2;
import org.gudy.azureus2.plugins.peers.PeerManager;
import org.gudy.azureus2.plugins.peers.PeerReadRequest;
import org.gudy.azureus2.plugins.peers.PeerStats;
import org.gudy.azureus2.pluginsimpl.local.messaging.MessageAdapter;
import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl;
import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl.PluginLimitedRateGroup;

import com.aelitis.azureus.core.networkmanager.LimitedRateGroup;
import com.aelitis.azureus.core.peermanager.piecepicker.util.BitFlags;

public class PeerImpl extends LogRelation implements Peer {
    protected PeerManagerImpl manager;
    protected PEPeer delegate;

    private HashMap<Object, PEPeerListener> peer_listeners;

    private UtilitiesImpl.PluginLimitedRateGroupListener up_rg_listener;
    private UtilitiesImpl.PluginLimitedRateGroupListener down_rg_listener;

    private volatile boolean closed;

    /**
     * don't use me, use PeerManagerImpl.getPeerForPEPeer
     * 
     * @param _delegate
     */

    protected PeerImpl(PEPeer _delegate) {
        delegate = _delegate;

        manager = PeerManagerImpl.getPeerManager(delegate.getManager());
    }

    public void bindConnection(ConnectionStub stub) {
    }

    public PeerManager getManager() {
        return (manager);
    }

    public PEPeer getDelegate() {
        return (delegate);
    }

    public Connection getConnection() {
        return delegate.getPluginConnection();
    }

    public boolean supportsMessaging() {
        return delegate.supportsMessaging();
    }

    public Message[] getSupportedMessages() {
        com.aelitis.azureus.core.peermanager.messaging.Message[] core_msgs = delegate.getSupportedMessages();

        Message[] plug_msgs = new Message[core_msgs.length];

        for (int i = 0; i < core_msgs.length; i++) {
            plug_msgs[i] = new MessageAdapter(core_msgs[i]);
        }

        return plug_msgs;
    }

    public int getState() {
        int state = delegate.getPeerState();

        switch (state) {

            case PEPeer.CONNECTING: {
                return (Peer.CONNECTING);
            }
            case PEPeer.DISCONNECTED: {
                return (Peer.DISCONNECTED);
            }
            case PEPeer.HANDSHAKING: {
                return (Peer.HANDSHAKING);
            }
            case PEPeer.TRANSFERING: {
                return (Peer.TRANSFERING);
            }
        }

        return (-1);
    }

    public byte[] getId() {
        // we *really* don't want a plugin to accidentally change our peerid (e.g. the Stuffer plugin did this)
        // as this screws stuff up bigtime

        byte[] id = delegate.getId();

        if (id == null) {

            return (new byte[0]);
        }

        byte[] copy = new byte[id.length];

        System.arraycopy(id, 0, copy, 0, copy.length);

        return (copy);
    }

    public String getIp() {
        return (delegate.getIp());
    }

    public int getPort() {
        return (delegate.getPort());
    }

    public int getTCPListenPort() {
        return delegate.getTCPListenPort();
    }

    public int getUDPListenPort() {
        return delegate.getUDPListenPort();
    }

    public int getUDPNonDataListenPort() {
        return delegate.getUDPNonDataListenPort();
    }

    public final boolean[] getAvailable() {
        BitFlags bf = delegate.getAvailable();
        if (bf == null) {
            return (null);
        }
        return (bf.flags);
    }

    public boolean isPieceAvailable(int pieceNumber) {
        return delegate.isPieceAvailable(pieceNumber);
    }

    public boolean isTransferAvailable() {
        return (delegate.transferAvailable());
    }

    public boolean isDownloadPossible() {
        return delegate.isDownloadPossible();
    }

    public boolean isChoked() {
        return (delegate.isChokingMe());
    }

    public boolean isChoking() {
        return (delegate.isChokedByMe());
    }

    public boolean isInterested() {
        return (delegate.isInteresting());
    }

    public boolean isInteresting() {
        return (delegate.isInterested());
    }

    public boolean isSeed() {
        return (delegate.isSeed());
    }

    public boolean isSnubbed() {
        return (delegate.isSnubbed());
    }

    public long getSnubbedTime() {
        return delegate.getSnubbedTime();
    }

    public void setSnubbed(boolean b) {
        delegate.setSnubbed(b);
    }

    public PeerStats getStats() {
        return (new PeerStatsImpl(manager, this, delegate.getStats()));
    }

    public boolean isIncoming() {
        return (delegate.isIncoming());
    }

    public int getPercentDone() {
        return (delegate.getPercentDoneInThousandNotation());
    }

    public int getOutgoingRequestCount() {
        return (delegate.getOutgoingRequestCount());
    }

    public int[] getOutgoingRequestedPieceNumbers() {
        return (delegate.getOutgoingRequestedPieceNumbers());
    }

    public int getPercentDoneInThousandNotation() {
        return (delegate.getPercentDoneInThousandNotation());
    }

    public String getClient() {
        return (delegate.getClient());
    }

    public boolean isOptimisticUnchoke() {
        return (delegate.isOptimisticUnchoke());
    }

    public void setOptimisticUnchoke(boolean is_optimistic) {
        delegate.setOptimisticUnchoke(is_optimistic);
    }

    public void initialize() {
        throw (new RuntimeException("not supported"));
    }

    public List getExpiredRequests() {
        throw (new RuntimeException("not supported"));
    }

    public List getRequests() {
        throw (new RuntimeException("not supported"));
    }

    public int getNumberOfRequests() {
        throw (new RuntimeException("not supported"));
    }

    public int getMaximumNumberOfRequests() {
        throw (new RuntimeException("not supported"));
    }

    public int[] getPriorityOffsets() {
        throw (new RuntimeException("not supported"));
    }

    public boolean requestAllocationStarts(int[] base_priorities) {
        throw (new RuntimeException("not supported"));
    }

    public void requestAllocationComplete() {
        throw (new RuntimeException("not supported"));
    }

    public void cancelRequest(PeerReadRequest request) {
        throw (new RuntimeException("not supported"));
    }

    public boolean addRequest(PeerReadRequest request) {
        throw (new RuntimeException("not supported"));
    }

    private void createRGListeners() {
        up_rg_listener = new UtilitiesImpl.PluginLimitedRateGroupListener() {
            public void disabledChanged(PluginLimitedRateGroup group, boolean is_disabled) {
                if (closed) {

                    group.removeListener(this);
                }

                delegate.setUploadDisabled(group, is_disabled);
            }

            public void sync(PluginLimitedRateGroup group, boolean is_disabled) {
                if (closed) {

                    group.removeListener(this);
                }
            }
        };

        down_rg_listener = new UtilitiesImpl.PluginLimitedRateGroupListener() {
            public void disabledChanged(PluginLimitedRateGroup group, boolean is_disabled) {
                if (closed) {

                    group.removeListener(this);
                }

                delegate.setDownloadDisabled(group, is_disabled);
            }

            public void sync(PluginLimitedRateGroup group, boolean is_disabled) {
                if (closed) {

                    group.removeListener(this);
                }
            }
        };
    }

    public void addRateLimiter(RateLimiter limiter, boolean is_upload) {
        synchronized (this) {

            if (closed) {

                return;
            }

            PluginLimitedRateGroup wrapped_limiter = UtilitiesImpl.wrapLimiter(limiter, true);

            if (up_rg_listener == null) {

                createRGListeners();
            }

            if (is_upload) {

                wrapped_limiter.addListener(up_rg_listener);

            } else {

                wrapped_limiter.addListener(down_rg_listener);
            }

            delegate.addRateLimiter(wrapped_limiter, is_upload);
        }
    }

    public void removeRateLimiter(RateLimiter limiter, boolean is_upload) {
        synchronized (this) {

            PluginLimitedRateGroup wrapped_limiter = UtilitiesImpl.wrapLimiter(limiter, true);

            if (up_rg_listener != null) {

                if (is_upload) {

                    wrapped_limiter.removeListener(up_rg_listener);

                } else {

                    wrapped_limiter.removeListener(down_rg_listener);
                }
            }

            delegate.removeRateLimiter(wrapped_limiter, is_upload);
        }
    }

    public RateLimiter[] getRateLimiters(boolean is_upload) {
        LimitedRateGroup[] limiters = delegate.getRateLimiters(is_upload);

        RateLimiter[] result = new RateLimiter[limiters.length];

        int pos = 0;

        for (LimitedRateGroup l : limiters) {

            if (l instanceof PluginLimitedRateGroup) {

                result[pos++] = UtilitiesImpl.unwrapLmiter((PluginLimitedRateGroup) l);
            }
        }

        if (pos == result.length) {

            return (result);
        }

        RateLimiter[] result_mod = new RateLimiter[pos];

        System.arraycopy(result, 0, result_mod, 0, pos);

        return (result_mod);
    }

    public void close(String reason, boolean closedOnError, boolean attemptReconnect) {
        manager.removePeer(this, reason);
    }

    public int readBytes(int max) {
        throw (new RuntimeException("not supported"));
    }

    public int writeBytes(int max) {
        throw (new RuntimeException("not supported"));
    }

    protected void closed() {
        synchronized (this) {

            closed = true;

            if (up_rg_listener != null) {

                // tidy up

                LimitedRateGroup[] limiters = delegate.getRateLimiters(true);

                for (LimitedRateGroup l : limiters) {

                    if (l instanceof PluginLimitedRateGroup) {

                        ((PluginLimitedRateGroup) l).removeListener(up_rg_listener);
                    }
                }

                limiters = delegate.getRateLimiters(false);

                for (LimitedRateGroup l : limiters) {

                    if (l instanceof PluginLimitedRateGroup) {

                        ((PluginLimitedRateGroup) l).removeListener(down_rg_listener);
                    }
                }
            }
        }

        if (delegate instanceof PeerForeignDelegate) {

            ((PeerForeignDelegate) delegate).stop();
        }
    }

    public int getPercentDoneOfCurrentIncomingRequest() {
        return (delegate.getPercentDoneOfCurrentIncomingRequest());
    }

    public int getPercentDoneOfCurrentOutgoingRequest() {
        return (delegate.getPercentDoneOfCurrentOutgoingRequest());
    }

    public void addListener(final PeerListener l) {
        PEPeerListener core_listener = new PEPeerListener() {
            public void stateChanged(final PEPeer peer, // seems don't need this here
                    int new_state) {
                try {
                    l.stateChanged(new_state);

                } catch (Throwable e) {
                    Debug.printStackTrace(e);
                }
            }

            public void sentBadChunk(final PEPeer peer, // seems don't need this here
                    int piece_num, int total_bad_chunks) {
                try {
                    l.sentBadChunk(piece_num, total_bad_chunks);

                } catch (Throwable e) {
                    Debug.printStackTrace(e);
                }
            }

            public void addAvailability(final PEPeer peer, BitFlags peerHavePieces) {
            }

            public void removeAvailability(final PEPeer peer, BitFlags peerHavePieces) {
            }
        };

        delegate.addListener(core_listener);

        synchronized (this) {

            if (peer_listeners == null) {

                peer_listeners = new HashMap<Object, PEPeerListener>();
            }

            peer_listeners.put(l, core_listener);
        }
    }

    public void removeListener(PeerListener l) {
        PEPeerListener core_listener = null;

        synchronized (this) {

            if (peer_listeners != null) {

                core_listener = peer_listeners.remove(l);
            }
        }

        if (core_listener != null) {

            delegate.removeListener(core_listener);
        }
    }

    public void addListener(final PeerListener2 l) {
        PEPeerListener core_listener = new PEPeerListener() {
            public void stateChanged(final PEPeer peer, // seems don't need this here
                    int new_state) {
                fireEvent(PeerEvent.ET_STATE_CHANGED, new Integer(new_state));
            }

            public void sentBadChunk(final PEPeer peer, // seems don't need this here
                    int piece_num, int total_bad_chunks) {
                fireEvent(PeerEvent.ET_BAD_CHUNK, new Integer[] { new Integer(piece_num), new Integer(total_bad_chunks) });
            }

            public void addAvailability(final PEPeer peer, BitFlags peerHavePieces) {
                fireEvent(PeerEvent.ET_ADD_AVAILABILITY, peerHavePieces.flags);
            }

            public void removeAvailability(final PEPeer peer, BitFlags peerHavePieces) {
                fireEvent(PeerEvent.ET_REMOVE_AVAILABILITY, peerHavePieces.flags);
            }

            protected void fireEvent(final int type, final Object data) {
                try {
                    l.eventOccurred(new PeerEvent() {
                        public int getType() {
                            return (type);
                        }

                        public Object getData() {
                            return (data);
                        }
                    });
                } catch (Throwable e) {

                    Debug.printStackTrace(e);
                }
            }
        };

        delegate.addListener(core_listener);

        synchronized (this) {

            if (peer_listeners == null) {

                peer_listeners = new HashMap<Object, PEPeerListener>();
            }

            peer_listeners.put(l, core_listener);
        }
    }

    public void removeListener(PeerListener2 l) {
        PEPeerListener core_listener = null;

        synchronized (this) {

            if (peer_listeners != null) {

                core_listener = peer_listeners.remove(l);
            }
        }

        if (core_listener != null) {

            delegate.removeListener(core_listener);
        }
    }

    public boolean isPriorityConnection() {
        return (delegate.isPriorityConnection());
    }

    public void setPriorityConnection(boolean is_priority) {
        delegate.setPriorityConnection(is_priority);
    }

    public void setUserData(Object key, Object value) {
        delegate.setUserData(key, value);
    }

    public Object getUserData(Object key) {
        return (delegate.getUserData(key));
    }

    // as we don't maintain a 1-1 mapping between these and delegates make sure
    // that "equals" etc works sensibly

    public boolean equals(Object other) {
        if (other instanceof PeerImpl) {

            return (delegate == ((PeerImpl) other).delegate);
        }

        return (false);
    }

    public int hashCode() {
        return (delegate.hashCode());
    }

    /**
     * Core use only. This is not propogated to the plugin interface
     * 
     * @return PEPeer object associated with the plugin Peer object
     */
    public PEPeer getPEPeer() {
        return delegate;
    }

    // Pass LogRelation off to core objects

    /*
     * (non-Javadoc)
     * @see org.gudy.azureus2.core3.logging.LogRelation#getLogRelationText()
     */
    public String getRelationText() {
        return propogatedRelationText(delegate);
    }

    /*
     * (non-Javadoc)
     * @see org.gudy.azureus2.core3.logging.LogRelation#getQueryableInterfaces()
     */
    public Object[] getQueryableInterfaces() {
        return new Object[] { delegate };
    }

    public byte[] getHandshakeReservedBytes() {
        return delegate.getHandshakeReservedBytes();
    }
}
